/*
 * $Id: OperatorList.java,v 1.12 2003/06/29 10:52:30 znamenacek Exp $
 *
 * Copyright (c) Tomas Znamenacek
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

package net.sf.vofce.guts;

import java.util.Map;
import java.util.HashMap;

import org.apache.log4j.Category;

/**
 * A table of all operators supported by vofce.
 * All operators are kept in a hash, the key being their
 * syntax. Unary minus is treated special, so the keys in the
 * hash are unique.
 *
 * @author Tomas Znamenacek
 */
public class OperatorList {

	/** Logger instance. */
	protected static Category logger = Category.getRoot();

	/** An instance of this class. */
	protected static OperatorList instance = new OperatorList();

	/**
	 * Hash containing the operator instances.
	 */
	protected Map ops = new HashMap();

	/** Returns <code>OperatorList</code> instance. */
	public static OperatorList getInstance() {

		return instance;
	}
	
	/**
	 * Fills the operator table with operators.
	 */
	private OperatorList() {

		logger.info("OperatorList: Creating the operator map.");
	
		insert(
			new Variable("x", "x", "<mi>x</mi>"),
			new String[] {"x"}
		);
		
		//
		// CONSTANTS
		// 
		
		insert(
			new Constant("e", "e", "<mi>e</mi>", Math.E),
			new String[] {"e"}
		);
		
		insert(
			new Constant("pi", "\\pi", "<mi>pi</mi>", Math.PI),
			new String[] {"pi", "p"}
		);
	
		//
		// UNARY OPERATORS
		//
		
        insert(
            new UnaryOperator(8, "floor{0}", "\\lfloor {x} \\rfloor", muf("floor")) {
				public double operation(double a) {
					return Math.floor(a);
				}
			}, new String[] {"floor"}
		);

		insert(
			new UnaryOperator(8, "sin{0}", tuf("sin"), muf("sin")) {
				public double operation(double a) {
					return Math.sin(a);
				}
			}, new String[] {"sin"}
		);
		
		insert(
			new UnaryOperator(8, "cos{0}", tuf("cos"), muf("cos")) {
				public double operation(double a) {
					return Math.cos(a);
				}
			}, new String[] {"cos"}
		);
		
		insert(
			new UnaryOperator(8, "tan{0}", tuf("tan"), muf("tan")) {
				public double operation(double a) {
					return Math.tan(a);
				}
			}, new String[] {"tan", "tg"}
		);
		
		insert(
			new UnaryOperator(8, "cotg{0}", tuf("cot"), muf("cotg")) {
				public double operation(double a) {
					return (1/Math.tan(a));
				}
			}, new String[] {"cotan", "cotg", "cot"}
		);
		
		insert(
			new UnaryOperator(8, "asin{0}", tuf("arcsin"), muf("asin")) {
				public double operation(double a) {
					return Math.asin(a);
				}
			}, new String[] {"asin", "arcsin"}
		);
		
		insert(
			new UnaryOperator(8, "acos{0}", tuf("arccos"), muf("acos")) {
				public double operation(double a) {
					return Math.acos(a);
				}
			}, new String[] {"acos", "arccos", "arcos"}
		);
		
		insert(
			new UnaryOperator(8, "atan{0}", tuf("arctan"), muf("atan")) {
				public double operation(double a) {
					return Math.atan(a);
				}
			}, new String[] {"atan", "atg", "arctan", "arctg"}
		);
		
		insert(
			new UnaryOperator(8, "abs{0}", "\\|{0}\\|", muf("abs")) {
				public double operation(double a) {
					return Math.abs(a);
				}
			}, new String[] {"abs"}
		);
	
		// should be log10?
		insert(
			new UnaryOperator(8, "log{0}", tuf("lg"), muf("log")) {
				public double operation(double a) {
					return Math.log(a);
				}
			}, new String[] {"log"}
		);
		
		insert(
			new UnaryOperator(8, "ln{0}", tuf("ln"), muf("ln")) {
				public double operation(double a) {
					return Math.log(a);
				}
			}, new String[] {"ln"}
		);
		
		insert(
			new UnaryOperator(8, "sgn{0}", tuf("sgn"), muf("sgn")) {
				public double operation(double a) {
					return (a == 0) ? 0 : (a > 0) ? 1 : -1;
				}
			}, new String[] {"sgn", "sig"}
		);
		
		//
		// BINARY OPERATORS
		// 
		
		insert(
			new BinaryOperator(1, "{0}+{1}", tbf("+"), mbf("+")) {
				public double operation(double a, double b) {
					return a+b;
				}
			}, new String[] {"+"}
		);
		
		insert(
			new BinaryOperator(1, "{0}-{1}", tbf("-"), mbf("-")) {
				public double operation(double a, double b) {
					return a-b;
				}
			}, new String[] {"-"}
		);
		
		insert(
			new BinaryOperator(5, "{0}*{1}", tbf("*"), mbf("*")) {
				public double operation(double a, double b) {
					return a*b;
				}
			}, new String[] {"*"}
		);
		
		insert(
			new BinaryOperator(5, "{0}/{1}",
			"\\frac'{'{0}'}''{'{1}'}'",
			"<mfrac><mrow>{0}</mrow><mrow>{1}</mrow></mfrac>") {
				public double operation(double a, double b) {
					return a/b;
				}
			}, new String[] {"/"}
		);
		
		insert(
			new BinaryOperator(7, "{0}^{1}", tbf("^"),
			"<msup><mrow>{0}</mrow><mrow>{1}</mrow></msup>") {
				public double operation(double a, double b) {
					return Math.pow(a, b);
				}
			}, new String[] {"^", "**"}
		);
		
		logger.info("OperatorList: Finished creating the operator map (" + ops.size() + " operators).");
	}

	/**
	 * Insert new operator into the list.
	 * Doesn't complain if some operator already exists
	 * under the same syntax, such errors are design-time
	 * and error checking would slow down the code.
	 */
	public void insert(Operator op, String[] syntax) {

		for (int i = 0; i < syntax.length; i++) {

			if (ops.containsKey(syntax[i])) {
				logger.debug("Ovewriting operator: " + syntax[i]);
			}

			ops.put(syntax[i], op);
		}
	}

	/**
	 * Returns operator from map.
	 */
	public Operator get(String key) {

		return (Operator) ops.get(key);
	}
	
	/**
	 * Returns all the syntaxes that represent some operator.
	 */
	public String[] syntaxes() {

		return (String[]) ops.keySet().toArray(new String[] {});
	}

	//
	// Syntax helpers
	//
	
	/**
	 * MathML unary function shorthand.
	 */
	protected String muf(String opName) {

		return "<mi>" + opName + "</mi><mo>&ApplyFunction;</mo><mrow>{0}</mrow>";
	}

	/**
	 * MathML binary function shorthand. 
	 */
	protected String mbf(String opName) {

		return "<mrow>{0}</mrow><mo>" + opName + "</mo><mrow>{1}</mrow>";
	}
	
	/**
	 * TeX unary function shorthand. 
	 */
	protected String tuf(String opName) {

		return "\\" + opName + "'{'{0}'}'";
	}

	/**
	 * TeX binary function shorthand.
	 */
	protected String tbf(String opName) {

		return "'{'{0}'}'" + opName + "'{'{1}'}'";
	}
}
